package com.java.base.io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

public class NIOServer {

    static Logger logger = Logger.getLogger(NIOServer.class.getSimpleName());

    static {
        logger.setLevel(Level.INFO);
    }

    int port;

    public NIOServer(int port) {
        this.port = port;
    }

    public void run() throws IOException {
        ServerSocketChannel serverSocketChannel = open(port);

        serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 1024);
        Selector selector = registerAccept(serverSocketChannel);

        while (serverSocketChannel.isOpen()) {
            Iterator<SelectionKey> iterator = selectAccept(selector).iterator();

            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                handle(selectionKey);
            }

        }

    }


    public void handle(SelectionKey selectionKey) throws IOException {
        if (selectionKey.isAcceptable()) {
            handleAcceptable(selectionKey);
        } else if (selectionKey.isReadable()) {
            handleReadable(selectionKey);
        } else if (selectionKey.isWritable()) {
            handleWritable(selectionKey, "OK");
        } else if (selectionKey.isConnectable()) {
            logger.info(selectionKey.toString() + " is connectable");
        } else if (selectionKey.isValid()) {
            logger.info(selectionKey.toString() + " is valid");
        } else {
            logger.info(selectionKey.toString() + " is nothing to ready");
        }

    }

    public void handleReadable(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        int i = socketChannel.read(byteBuffer);
        if (i <= 0) {
            socketChannel.close();
        } else {
            String s = "received=" + new String(byteBuffer.array(), 0, i) + "\r\n";
            logger.warning(s);
//        selectionKey.interestOps(SelectionKey.OP_WRITE);
//        selectionKey.interestOps(SelectionKey.OP_READ);
            handleWritable(selectionKey, s);

        }
    }
    public void handleWritable(SelectionKey selectionKey, String msg) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put(("Server " + msg).getBytes(StandardCharsets.UTF_8));
        byteBuffer.flip();
        socketChannel.write(byteBuffer);
        selectionKey.interestOps(SelectionKey.OP_READ);
    }

    public void handleAcceptable(SelectionKey selectionKey) throws IOException {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
    }


    public ServerSocketChannel open(int port) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(0xbbbb));
        logger.info("Server Port:" + port);
        return serverSocketChannel;
    }

    public Selector registerAccept(ServerSocketChannel serverSocketChannel) throws IOException {
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        return selector;
    }

    public Set<SelectionKey> selectAccept(Selector selector) throws IOException {
        int keyNum = 0;
        while (keyNum == 0) {
            keyNum = selector.select(20000);
            logger.info("Already Accept Num:" + keyNum);
        }
        return selector.selectedKeys();
    }

    public static void main(String[] args) throws IOException {
        logger.info("Start...");
        NIOServer nioServer = new NIOServer(0xbbbb);
        nioServer.run();
    }

}
